﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: RelationsManager.cs
//
//  Description: Manages relations between persistents.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Chili.Opf3.Mapping;

namespace Chili.Opf3.Relations
{
	/// <summary>
	/// Manages the relations between the objects.
	/// </summary>
	/// <remarks>
	/// This class manages the relations between two or more objects. It is used
	/// by the <see cref="Opf3.ObjectContext">ObjectContext</see> to persist changes
	/// in the related objects and update the connections between related objects.
	/// </remarks>
	internal sealed class RelationsManager
	{
		private ObjectContext _context;
		private bool _automaticallyLoadRelatedObjects = true;
		private bool _useRemotingSerialization;

		/// <summary>
		/// Creates a new instance of the <see cref="RelationsManager">RelationsManager
		/// Class</see>.
		/// </summary>
		/// <param name="context">
		/// 	<see cref="Opf3.ObjectContext">ObjectContext</see> that holds the
		/// <see cref="RelationsManager">RelationsManager</see> and is used by the
		/// <see cref="RelationsManager">RelationsManager</see>
		/// </param>
		public RelationsManager(ObjectContext context)
		{
			if (context == null)
				throw new ArgumentNullException("context");

			_context = context;
		}

		/// <summary>
		/// Gets or sets a value that indicates whether related objects should be loaded automatically when
		/// invoking the InnerObject property of the <see cref="ObjectHolder{T}" />, <see cref="ObjectSetHolder{T}" /> 
		/// or <see cref="ObjectListHolder{T}" />. If the behaviour (default is true) is changed the related objects 
		/// can only be loaded by using the Load method of the object holder objects. After calling the Load 
		/// method the InnerObject property will hold the related objects.
		/// </summary>
		public bool AutomaticallyLoadRelatedObjects
		{
			get { return _automaticallyLoadRelatedObjects; }
			set { _automaticallyLoadRelatedObjects = value; }
		}

		/// <summary>
		/// Gets or sets whether the objects (and its relations) are serialized for .NET remoting over binary channels. 
		/// If the object is serialized for remoting lazy loading of relations is enabled. Otherwise lazy loading 
		/// is disabled. This option should only get enabled for remoting over binary channels and does not work 
		/// with Windows Communication Foundation or web services in general.
		/// </summary>
		public bool UseRemotingSerialization
		{
			get { return _useRemotingSerialization; }
			set { _useRemotingSerialization = value; }
		}

		/// <summary>Connects the related persistent objects to the given persistent object.</summary>
		/// <param name="obj">Persistent object that's relations mappings are created.</param>
		public void ConnectRelated(object obj)
		{
			if (obj == null)
				throw new ArgumentNullException("obj");

			TypeMapping typeMapping = TypeMapping.GetTypeMapping(obj);

			// Loop over all relation mappings and populate them with data.
			foreach (RelationInfo info in typeMapping.Relations)
			{
				// Get the objectHolder.
				ObjectHolderBase objectHolder = info.GetObjectHolder(obj);
				// Skip if the holder is null.
				if (objectHolder == null)
					continue;

				// Set the current context, the relation attribute and parent object to the object holder.
				objectHolder.SetObjectHolderData(_context, info.RelationAttribute, obj, _automaticallyLoadRelatedObjects, _useRemotingSerialization);
			}
		}

		/// <summary>
		/// Connects all child object's foreign-key properties to the given persistent object's identifier.
		/// </summary>
		/// <param name="obj">The parent object that is saved.</param>
		public void ConnectPropertiesInChildObjects(object obj)
		{
			// Skip if object is marked for deletion.
			ObjectInfo objectInfo = _context.GetObjectInfo(obj);
			if (objectInfo.MarkForDeletion)
				return;

			TypeMapping typeMapping = TypeMapping.GetTypeMapping(obj);

			// Loop over all relation mappings and populate them with data.
			foreach (IRelationInfo info in typeMapping.Relations)
			{
				if (info.RelationAttribute.PersistRelationship != PersistRelationships.ParentFirst)
					continue;

				// Get the objectHolder.
				ObjectHolderBase objectHolder = info.GetObjectHolder(obj);
				// Skip if the holder is null.
				if (objectHolder == null)
					continue;

				// Connect the foreign key properties in object holder with the identifier of the parent object.
				objectHolder.ConnectPropertiesInChildObjects();
			}
		}

		/// <summary>Persists the changes on related persistent objects.</summary>
		/// <param name="obj">Persistent object that's related persistent objects are persisted.</param>
		/// <param name="persistRelationship"><see cref="Opf3.Relations.PersistRelationships">PersistRelationship</see> 
		/// specifying which object in a relation is saved first.</param>
		/// <param name="persistDepth">Specifies the depth of the loaded object graph that is persisted.</param>
		/// <param name="persistentOperation">Specifies the operation on the parent persistent object.</param>
		public void PersistRelated(object obj, PersistRelationships persistRelationship, PersistDepths persistDepth, PersistentOperations persistentOperation)
		{
			// Check if we need to invoke the methods of the IRelationsNotification interface.
			IRelationsNotification rn = obj as IRelationsNotification;
			if (rn != null)
				rn.OnBeforeRelationsPersist(new RelationsNotificationEventArgs(_context, persistRelationship, persistentOperation));

			TypeMapping typeMapping = TypeMapping.GetTypeMapping(obj);

			// Loop over all relation mappings and populate them with data.
			foreach (IRelationInfo info in typeMapping.Relations)
			{
				// Persist the changes on the ObjectHolder.
				if (info.RelationAttribute.PersistRelationship == persistRelationship)
				{
					if (persistentOperation == PersistentOperations.Insert && (info.RelationAttribute.PersistBehaviours & PersistBehaviours.DontInsert) != 0)
						continue;
					else if (persistentOperation == PersistentOperations.Update && (info.RelationAttribute.PersistBehaviours & PersistBehaviours.DontUpdate) != 0)
						continue;
					
					// Get the objectHolder.
					ObjectHolderBase objectHolder = info.GetObjectHolder(obj);
					// Skip if the holder is null.
					if (objectHolder == null)
						continue;

					objectHolder.PersistChanges(_context, persistDepth);
				}
			}

			// Check if we need to invoke the methods of the IRelationsNotification interface.
			if (rn != null)
				rn.OnAfterRelationsPersist(new RelationsNotificationEventArgs(_context, persistRelationship, persistentOperation));
		}

		/// <summary>
		/// Checks if one of the related objects has been modified.
		/// </summary>
		/// <param name="obj">Persistent object that's related persistent objects are inspected.</param>
		public bool IsRelatedModified(object obj)
		{
			TypeMapping typeMapping = TypeMapping.GetTypeMapping(obj);

			// Loop over all relation mappings and populate them with data.
			foreach (IRelationInfo info in typeMapping.Relations)
			{
				// Get the objectHolder.
				ObjectHolderBase objectHolder = info.GetObjectHolder(obj);
				// Skip if the holder is null.
				if (objectHolder == null)
					continue;

				// check if the object holder has removed objects.
				if (objectHolder.HasRemovedObjects())
					return true;

				// check if the object holder has a modified object.
				if (objectHolder.HasModifiedObjects(_context))
					return true;
			}

			return false;
		}
	}
}
